using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using ContentOverContainer.Common;
using ContentOverContainer.SimpleSyntax;
using Roslyn.Services;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;

namespace ContentOverContainer
{
    /// <summary>
    /// Class containing various utility methods as extensions.
    /// </summary>
    /// <author>Harald Binkle</author>
    public static class Extensions
    {

        #region "Remove Attribute methods"

        /// <summary>
        /// Removes all multiplicity attributes out of the syntay list.
        /// </summary>
        /// <param name="pRootNode">The root node.</param>
        /// <param name="pAttributeList">The attribute list.</param>
        /// <param name="pTurnIntoComment">if set to <c>true</c> [p turn into comment].</param>
        /// <returns>the new node</returns>
        /// <author>Harald Binkle</author>
        public static SyntaxNode RemoveMultiplicityAttribute(this SyntaxNode pRootNode,
                                                             SyntaxList<AttributeListSyntax> pAttributeList,
                                                             bool pTurnIntoComment = false)
        {
            var syntaxNode = pRootNode;
            if (pAttributeList.Count == 1) //case it's the only attribute
            {
                syntaxNode = syntaxNode.RemoveNodes(pAttributeList, SyntaxRemoveOptions.KeepExteriorTrivia);
                //add att as leading comment trivia to the node
                if (pTurnIntoComment)
                //TODO: does not yet work properly if other Attributes are within the same brackets
                {
                    syntaxNode = syntaxNode.AddMultiplicityAttributeNodeAsComment(pAttributeList[0]);
                }
            }
            else
            {

                var multiplicityAtt = pAttributeList.GetMultiplicityAttributes();
                if (multiplicityAtt != null)
                {
                    foreach (AttributeSyntax att in multiplicityAtt)
                    {
                        var attributeListSyntax = att.Parent;
                        syntaxNode = syntaxNode.RemoveNode(att.Parent, SyntaxRemoveOptions.KeepExteriorTrivia);
                        //add att as leading comment trivia to the node
                        if (pTurnIntoComment) //TODO: does not yet work properly
                        {
                            syntaxNode = syntaxNode.AddMultiplicityAttributeNodeAsComment(attributeListSyntax);
                        }
                    }
                }
            }
            return syntaxNode;
        }

        /// <summary>
        /// Adds the node as comment.
        /// </summary>
        /// <param name="pNode">The p node.</param>
        /// <param name="pNodeAsComment">The node as comment.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static SyntaxNode AddMultiplicityAttributeNodeAsComment(this SyntaxNode pNode, SyntaxNode pNodeAsComment)
        {
            SyntaxTrivia newComment;
            if (pNodeAsComment.Kind == SyntaxKind.AttributeList)
            {
                var attSynList = pNodeAsComment as AttributeListSyntax;
                if ((attSynList != null) && (attSynList.Attributes.Count > 1))
                {
                    AttributeSyntax multiAtt = null;
                    var sepList = new SeparatedSyntaxList<AttributeSyntax>();
                    foreach (var att in attSynList.Attributes)
                    {
                        if (MultiplicityAttribute.EqualsName(((IdentifierNameSyntax)(att.Name)).Identifier.ValueText))
                        {
                            multiAtt = att;
                            continue;
                        }
                        sepList = sepList.Add(att);
                    }
                    var attList = Syntax.AttributeList(sepList);

                    newComment = Syntax.SyntaxTrivia(SyntaxKind.SingleLineCommentTrivia,
                                                     attList.ToString() +
                                                     (multiAtt != null ? "//" + multiAtt.ToString() : String.Empty) +
                                                     Environment.NewLine);
                }
                else
                    newComment = Syntax.SyntaxTrivia(SyntaxKind.SingleLineCommentTrivia,
                                                     "//" + pNodeAsComment.ToString() + Environment.NewLine);
            }
            else return pNode;

            var newTriviaList = pNode.GetLeadingTrivia().ToList();
            try
            {
                //find the last non whitespace trivia
                var lastNonWhitespace =
                    newTriviaList.Last(
                        x => (x.Kind != SyntaxKind.WhitespaceTrivia) && (x.Kind != SyntaxKind.EndOfLineTrivia));
                var lastIndex = newTriviaList.IndexOf(lastNonWhitespace);
                //keep a newline if exists
                if ((newTriviaList.Count > lastIndex + 1) &&
                    (newTriviaList[lastIndex + 1].Kind == SyntaxKind.EndOfLineTrivia))
                    lastIndex++;
                //remove unncessary whitespaces
                newTriviaList.RemoveRange(lastIndex + 1, newTriviaList.Count - lastIndex - 1);
            }
            catch (InvalidOperationException e) //newTriviaList.Last may fail
            {
                Debug.WriteLine(e.ToString());
            }


            newTriviaList.Add(newComment);
            var triList = Syntax.TriviaList(newTriviaList);
            return pNode.WithLeadingTrivia(triList);
        }

        #endregion




        #region "Find References"

        /// <summary>
        /// Finds all references.
        /// </summary>
        /// <param name="pNode">The node.</param>
        /// <param name="pSemModel">The semantic model.</param>
        /// <param name="pSolution">The solution.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static IEnumerable<CommonSyntaxNode> FindAllReferences(this VariableDeclarationSyntax pNode,
                                                                      ISemanticModel pSemModel, ISolution pSolution)
        {
            //http://connect.microsoft.com/VisualStudio/feedback/details/759204/getdeclaredsymbol-no-longer-works-on-fielddeclarationsyntax-in-v1-1
            var syms = pNode.Variables.Select(v => pSemModel.GetDeclaredSymbol(v));

            return FindNodesForSymbols(pSolution, syms);
        }

        /// <summary>
        /// Finds all references.
        /// </summary>
        /// <param name="pNode">The node.</param>
        /// <param name="pSemModel">The semantic model.</param>
        /// <param name="pSolution">The solution.</param>
        /// <returns></returns>
        /// <author>
        /// Harald Binkle
        /// </author>
        public static IEnumerable<CommonSyntaxNode> FindAllReferences(this SyntaxNode pNode, ISemanticModel pSemModel,
                                                                      ISolution pSolution)
        {
            var sym = pSemModel.GetDeclaredSymbol(pNode);
            var syms = Enumerable.Repeat(sym, 1);

            return FindNodesForSymbols(pSolution, syms);
        }

        /// <summary>
        /// Finds the nodes for symbols.
        /// </summary>
        /// <param name="pSolution">The solution.</param>
        /// <param name="syms">The symbols.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        private static IEnumerable<CommonSyntaxNode> FindNodesForSymbols(ISolution pSolution,
                                                                         IEnumerable<ISymbol> pSymbols)
        {
            var result = from symbol in pSymbols
                         from reference in symbol.FindReferences(pSolution)
                         from loc in reference.Locations
                         let position = loc.Location.SourceSpan.Start
                         select loc.Location.SourceTree.GetRoot().FindToken(position)
                         into token
                         select token.Parent;

            return result;
        }

        #endregion

        /// <summary>
        /// Replaces the nodes within one document and calls AddMethod.
        /// </summary>
        /// <param name="pRoot">The p root.</param>
        /// <param name="pDocument">The p document.</param>
        /// <param name="pReplacements">The p replacements.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static SyntaxNode ReplaceNodes(this CommonSyntaxNode pRoot, IDocument pDocument, ChangeNodeInfoList pReplacements)
        {
            var nodesOfDoc = pReplacements.Where(x => x.Document.IsEquivalentTo(pDocument));

            var nodesToReplace = nodesOfDoc.Select(x => x.OrgSyntaxNode).Cast<CommonSyntaxNode>();

            var methodsToAdd = new List<ChangeNodeMethodSubstitution>();

            var newroot = pRoot.ReplaceNodes(nodesToReplace,
                                             new Func<CommonSyntaxNode, CommonSyntaxNode, CommonSyntaxNode>(
                                                 (orgNode, newerNode) =>
            {
                foreach (var changeNodeInfo in nodesOfDoc)
                {
                    if (changeNodeInfo.OrgSyntaxNode.IsEquivalentTo(newerNode))
                    {
                        if (changeNodeInfo.MethodSubstitutionList.Count>0)
                        {
                            methodsToAdd.AddRange(changeNodeInfo.MethodSubstitutionList);
                        }

                        ChangeNodeInfo elementDone = changeNodeInfo;
                        pReplacements.Remove(elementDone);
                        return elementDone.NewSyntaxNode;
                    }
                }

                return newerNode;
                //throw new ArgumentException("No replacement node found", "old");
            }));

            if (methodsToAdd.Any())
                newroot = AddMethods(newroot, methodsToAdd);

            return (SyntaxNode)newroot;
        }



        /// <summary>
        /// Adds the new generated methods to the appropriate class in the document.
        /// </summary>
        /// <param name="pRoot">The p root.</param>
        /// <param name="pMethodsToAdd">The p methods to add.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        private static CommonSyntaxNode AddMethods(this CommonSyntaxNode pRoot, List<ChangeNodeMethodSubstitution> pMethodsToAdd)
        {
            var newroot = new SimpleSyntaxMultiplicityRewriter(pMethodsToAdd).Visit(pRoot as SyntaxNode);
            return newroot;
        }

        /// <summary>
        /// Gets the type of the declarated variable. In case of a method declaration the return type is the result.
        /// </summary>
        /// <param name="declNode">The decl node.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static TypeSyntax GetDeclarationType(this MemberDeclarationSyntax declNode)
        {
            TypeSyntax type = null;
            if (declNode is FieldDeclarationSyntax)
                type = ((FieldDeclarationSyntax)declNode).Declaration.Type;
            if (declNode is PropertyDeclarationSyntax)
                type = ((PropertyDeclarationSyntax)declNode).Type;
            if (declNode is MethodDeclarationSyntax)
                type = ((MethodDeclarationSyntax)declNode).ReturnType;
            if (type == null)
                throw new NotImplementedException("Could not determine return type of: " + declNode);
            return type;
        }


        /// <summary>
        /// Gets the first ExpressionSyntax ancester.
        /// </summary>
        /// <param name="reference">The reference.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static ExpressionSyntax GetExpression(this SyntaxNode reference)
        {
            return reference.Parent.FirstAncestorOrSelf<ExpressionSyntax>();
        }

        /// <summary>
        /// Gets the first StatementSyntax ancester.
        /// </summary>
        /// <param name="reference">The reference.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static StatementSyntax GetParentStatement(this SyntaxNode reference)
        {
            return reference.Parent.FirstAncestorOrSelf<StatementSyntax>();
        }

        /// <summary>
        /// Gets the first fist ancester BlockSyntax.
        /// </summary>
        /// <param name="reference">The reference.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static BlockSyntax GetParentBlock(this SyntaxNode reference)
        {
            return reference.Parent.FirstAncestorOrSelf<BlockSyntax>();
        }




        /// <summary>
        /// Determines the member MemberDeclarationSyntax for the given SimpleNameSyntax in the passed ISemanticModel.
        /// </summary>
        /// <param name="exprName">Name of the expr.</param>
        /// <param name="pSem">The p sem.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static MemberDeclarationSyntax GetMemberDeclaration(this SimpleNameSyntax pExprName, ISemanticModel pSem)
        {
            return GetDeclaration<MemberDeclarationSyntax>(pExprName, pSem) as MemberDeclarationSyntax;
        }


        /// <summary>
        /// Finds the VariableDeclaratorSyntax of the given SimpleNameSyntax.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="exprName">Name of the expr.</param>
        /// <param name="pSem">The p sem.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static CommonSyntaxNode GetDeclaration<T>(this SimpleNameSyntax pExprName, ISemanticModel pSem)
            where T : class
        {
            var declNodes = pSem.GetSymbolInfo(pExprName).Symbol.OriginalDefinition.DeclaringSyntaxNodes;
            if (declNodes.Count == 0) //case it's a call to a member not defined in this solution
            {
                return null;
            }

            var result = declNodes[0];
            if (result is VariableDeclaratorSyntax)
            {
                if (result.Parent is T)
                    result = result.Parent;

                if (result.Parent != null && result.Parent.Parent is T)
                    result = result.Parent.Parent;
            }

            return result;
        }

        /// <summary>
        /// Finds the VariableDeclaratorSyntax of the given SimpleNameSyntax by calling GetDeclaration<T>(this SimpleNameSyntax pExprName, ISemanticModel pSem)
        /// </summary>
        /// <param name="exprName">Name of the expr.</param>
        /// <param name="pSem">The p sem.</param>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        public static VariableDeclarationSyntax GetVariableDeclaration(this SimpleNameSyntax pExprName, ISemanticModel pSem)
        {
            return GetDeclaration<VariableDeclarationSyntax>(pExprName, pSem) as VariableDeclarationSyntax;
        }



    }
}
